<?php

namespace core;

use ErrorException;
use Throwable;

/**
 * 将 PHP 错误和异常统一处理
 */
class AppException
{
    /** @var bool 是否开启调试模式（显示详细错误）*/
    private static bool $debug;

    /** @var bool 是否已注册 */
    private static bool $registered = false;

    /**
     * 静态注册方法
     *
     * @param bool $debug 是否开启调试模式
     */
    public static function register(bool $debug = false): void
    {
        if (self::$registered) {
            return; // 避免重复注册
        }

        self::$debug      = $debug;
        self::$registered = true;

        // 注册后，E_WARNING, E_NOTICE 等都会被 onPhpError 捕获
        set_error_handler([self::class, 'onPhpError']);
        // 捕获所有未被 try/catch 的异常 (包括 onPhpError 抛出的 ErrorException)
        set_exception_handler([self::class, 'onException']);
        // 捕获致命错误 (E_ERROR)
        register_shutdown_function([self::class, 'onShutdown']);
    }

    /**
     * [set_error_handler] 将 PHP 错误转为 ErrorException，统一走异常流
     *
     * @param int $errno
     * @param string $errstr
     * @param string $errfile
     * @param int $errline
     * @return bool
     * @throws ErrorException
     */
    public static function onPhpError(int $errno, string $errstr, string $errfile, int $errline): bool
    {
        if (!(error_reporting() & $errno)) {
            return false; // 此错误被 @ 抑制，交还 PHP 默认处理（即什么都不做）
        }

        // 将所有 PHP 错误（Warning, Notice等）抛出为异常
        throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    }

    /**
     * [set_exception_handler] 统一处理所有未捕获的异常和错误
     *
     * @param Throwable $e 捕获所有 Error 和 Exception
     * @return void
     */
    public static function onException(Throwable $e): void
    {       
        if (self::$debug) {
            $file_path = CORE_PATH . '/templates/debug.php';
            
            if (is_file($file_path)) {
                // 获取异常信息
                $error_info = [
                    'message'     => $e->getMessage(),
                    'file'        => $e->getFile(),
                    'line'        => $e->getLine(),
                    'source_code' => self::getErrorSourceCode($e->getFile(), $e->getLine()),
                ];

                require_once $file_path;
            }
        }

    }

    /**
     * [register_shutdown_function] 处理致命错误
     *
     * 捕获 E_ERROR, E_PARSE 等
     * @return void
     */
    public static function onShutdown(): void
    {
        //获取最后发生的错误
        $error = error_get_last();
        // 定义我们关心的致命错误类型
        $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];

        if ($error !== null && in_array($error['type'], $fatalErrors, true)) {
            if (self::$debug) {
                // 如果可能，清除任何已缓冲的输出
                if (ob_get_level() > 0) {
                    ob_clean();
                }

                $error_info = [
                    'message'     => $error['message'],
                    'file'        => $error['file'],
                    'line'        => $error['line'],
                    'source_code' => self::getErrorSourceCode($error['file'], $error['line']),
                ];

                // 包含错误模板
                require __DIR__ . '/templates/debug.php';
            } else {
                // 生产环境：不显示错误，返回 500 状态码
                require __DIR__ . '/templates/error.php';
                // 强烈建议：在此处添加日志记录
            }
        }
    }

    /**
     * 获取出错行的上下文源码 (带高亮)
     *
     * @param string $file 出错文件路径
     * @param int    $line 出错行号
     * @param int $contextLines 上下文显示的行数 (前后各 $contextLines 行)
     * @return string 格式化后的 HTML 源码字符串
     */
    private static function getErrorSourceCode(string $file, int $line, int $contextLines = 5): string
    {
        if (!file_exists($file) || !is_readable($file)) {
            $errorMsg = 'Source file not found or unreadable: ' . htmlspecialchars($file, ENT_QUOTES, 'UTF-8');
            return "{$errorMsg}";
        }

        $fileLines = @file($file, FILE_IGNORE_NEW_LINES);

        if ($fileLines === false) {
            $errorMsg = 'Could not read source file: ' . htmlspecialchars($file, ENT_QUOTES, 'UTF-8');
            return "{$errorMsg}";
        }

        $totalLines = count($fileLines);
        $startLine  = max(1, $line - $contextLines);
        $endLine    = min($totalLines, $line + $contextLines);

        // 生成带高亮的源码
        $sourceCode = "";
        for ($i = $startLine; $i <= $endLine; $i++) {
            if (!isset($fileLines[$i - 1])) {
                continue;
            }

            // 获取、清理和转义代码行
            $lineContent = htmlspecialchars($fileLines[$i - 1], ENT_QUOTES, 'UTF-8');
            if ($i === $line) {
                // 高亮出错行
                $sourceCode .= "<div id='target' class='line'><span class='index'>{$i}</span> $lineContent</div>";
            } else {
                // 普通行
                $sourceCode .= "<div class='line'><span class='index'>{$i}</span> $lineContent</div>";
            }
        }

        return $sourceCode;
    }
}
